/** * DefaultSelectModel - Model which holds a list and the selected item * * Copyright (c) 2002 * Marty Phelan, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.taursys.model; import java.text.Format; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import com.taursys.util.DataTypes; import com.taursys.util.UnsupportedDataTypeException; import com.taursys.debug.Debug; /** * Implementation of the <code>SelectModel</code> which holds a list of options * and the selected item. This model can be used in a variety of ways. It can * be used in an un-bound mode, where the current selection is maintained * internally. It can also be used in a bound mode where the current selection * is propagated to a value holder. When used in the bound mode, either a * single property, or multiple properties can be set in the value holder. * <p> * The following sections describe the required settings to make for each * of the modes. * <p> * <b>Un-bound Mode (uses internal VariantValueHolder)</b> * <p> * When used in this mode this component uses an internal * <code>VariantValueHolder</code> to hold the current selection. By default a * <code>VariantValueHolder</code> is created with a data type of * <code>String</code>. To set a different data type use the constructor which * takes a data type as a parameter. (example * <code>new DefaultSelectModel(DataTypes.TYPE_INT)</code>). * <p> * To use this component in the un-bound mode, you must set the following * properties: * <ul> * <li><code>list</code> - should be set to a type of * <code>CollectionHolder</code> which holds the list of options * (see "Setting the List"). * </li> * <li><code>displayPropertyName</code> - the name of the property to display * (property of objects in list). Example: given a list of "Location" objects * with a property called "locationName", use * <code>setDisplayPropertyName("locationName")</code> * to display the zipCode. IMPORTANT - The displayed property choosen must * result in a unique list of values, otherwise the intended value may not be * selected/displayed. * </li> * <li><code>listPropertyNames</code> - set a single source property name in the * <code>list</code> objects. Example: Given a list containing "Location" * objects which has properties "zipCode", "cityName", "stateAbbr", and * "country", to make the zipcode the internal value use * <code>setListPropertyNames(new String[] {"zipCode"})</code>. * <p> * If the <code>list</code> is an <code>ObjectArrayValueHolder</code>, then * the property name should always be a single "value" (which is the default). * </li> * <li><code>propertyName</code> - must be "value" (which is the default). * </li> * <li><code>nullDisplay</code> - String to display in list for null selection. * Example: "--- Nothing Selected ---" * </li> * </ul> * <b>Bound Mode</b> * <p> * To use this component in the bound mode, use the same properties as * described in the Un-bound Mode, plus the following additional properties: * <ul> * <li><code>valueHolder</code> - should be set to the target * <code>ValueHolder</code> which contains the current selection and will be * updated if the selection is changed. * </li> * <li><code>propertyName</code> - set this to the first (or only) * <code>ValueHolder</code> object property name which will be bound to the * selection. Example: given a <code>ValueHolder</code> with an * "Address" object which has a "zipCode" property, use * <code>setPropertyName("zipCode")</code> to store the current selection in * the "Address.zipCode" property. * </li> * <li><code>propertyNames</code> - use this when you want to set more than * 1 property in the <code>ValueHolder</code> object. Example: assume you want * to set not only the "zipCode" property, but also the "city", "state" and * "country" properties, use * <code>setPropertyNames(new String[] {"zipCode","city","state","country"})</code> * </li> * <li><code>listPropertyNames</code> - set the to the source property name(s) * in the <code>list</code> objects. IMPORTANT - The name(s) of the * <code>listPropertyNames</code> properties MUST be * in the same ORDER as the <code>propertyName(s)</code>. The names in the * <code>listPropertNames</code> may be different than the names in the * <code>propertyNames</code> since they are associated * with in objects in the <code>list</code>, not the <code>valueHolder</code>. * Example: Given a <code>list</code> * containing "Location" objects which has properties "zipCode", "cityName", * "stateAbbr", and "country", for a single property use * <code>setListPropertyNames(new String[] {"zipCode"})</code>. For * multiple properties use * <code>setListPropertyNames(new String[] {"zipCode", "cityName", "stateAbbr", * "country"})</code> * <p> * If the <code>list</code> is an <code>ObjectArrayValueHolder</code>, then * the property name should always be a single "value" (which is the default). * </li> * </ul> * <b>Setting the List</b> * <p> * The <code>list</code> must be a type of <code>CollectionValueHolder</code> * (example: <code>VOCollectionValueHolder</code> or * <code>VOListValueHolder</code>). * The holder can contain any type of object (but they must all be instances * of the same class). * <p> * If the <code>list</code> is an <code>ObjectArrayValueHolder</code>, then the * <code>toString()</code> method is used as the display value (regardless of * the <code>displayPropertyName</code>). If used in the bound mode, the whole * object itself is stored in the target ValueHolder's object (regardless of * the property names listed in the <code>setListPropertyNames</code> method). * It is important to make sure that the <code>valueHolder</code> property * is the same type as the objects in the <code>ObjectArrayValueHolder</code> * or a <code>ModelException</code> will occur. * <p> * You can also preset the <code>list</code> in the constructor by passing it an * array of Objects to be used for the <code>list</code>. The resulting * <code>list</code> will be an <code>ObjectArrayValueHolder</code>. * <p> * <b>Other Important Information</b> * <p> * When a selection is made, the values are copied from the properties in the * <code>list</code> to the properties in the <code>valueHolder</code> object. * The property names in <code>propertyNames[]</code> and * <code>listPropertyNames[]</code> must appear in a corresponding order. * <p> * A "null" item will always be added to the <code>displayOptionList</code>. * When the "null" item is selected, a null will be assigned to the * <code>propertyNames[]</code> in the <code>valueHolder</code> object. * The actual "null" item to to display is defined by the * <code>nullDisplay</code> (default is "--none--"). * <p> * The <code>format</code> and <code>formatPattern</code> govern the display * property in this component. The <code>getText</code> method returns the * formatted display property, while the <code>setText</code> * method changes the current selection to one whose display matches the given * value. If you attempt to <code>setText</code> for an item that is not in * the list, a <code>NotInListException</code> will be thrown. */ public class DefaultSelectModel extends DefaultTextModel implements SelectModel { private com.taursys.model.CollectionValueHolder list; private String displayPropertyName = "value"; private String[] listPropertyNames = new String[] {"value"}; private String[] propertyNames = new String[] {"value"}; private String nullDisplay = "--none--"; private boolean nullAllowed = true; // ************************************************************************* // Constructors // ************************************************************************* /** * Constructs a new DefaultSelectModel * The default valueHolder for this model is a String VariantValueHolder. * The default list for this model is an ObjectArrayValueHolder with an * empty Object array. */ public DefaultSelectModel() { setList(createDefaultList()); } /** * Constructs new DefaultSelectModel and sets valueHolder to a * VariantValueHolder for given data type. * The default list for this model is an ObjectArrayValueHolder with an * empty Object array. * @param the data type for the VariantValueHolder (DataType.TYPE_XXXX) * @throws UnsupportedDataTypeException if invalid javaDataType is given * @see com.taursys.util.DataTypes */ public DefaultSelectModel(int javaDataType) throws UnsupportedDataTypeException { super(javaDataType); setList(createDefaultList()); } // ************************************************************************* // Default Creators // ************************************************************************* /** * Constructs a default list for this model. The default list is an * ObjectArrayValueHolder with an empty Object array. */ protected CollectionValueHolder createDefaultList() { return new ObjectArrayValueHolder(new Object[]{}); } // ************************************************************************* // Public Methods // ************************************************************************* /** * Returns display value of current selection. * @throws NotInListException if current valueHolder object values do not * match any item in list. * @throws ModelException if problem while matching properties of valueHolder * object to list object. */ public String getText() throws com.taursys.model.ModelException { if (positionListToCurrentSelection()) return getCurrentDisplayValue(); else return nullDisplay; } /** * Sets the current selection by matching the given value to the list's display values. * This method also copies the corresponding values from the selected list * object to the valueHolder object. If the given value is null or a value * matching the nullDisplay, this model will set the propertyNames[] in the * valueHolder object to null. * @throws NotInListException if the given value does not match any item in list. * @throws ModelException if problem while setting properties of object in * valueHolder or fetching display values from the list. */ public void setText(String value) throws ModelException { if (value == null || value.equals(nullDisplay)) { if (nullAllowed) { setNullValues(); return; } else { throw new NotInListException( NotInListException.GIVEN_VALUE_NOT_IN_LIST, value); } } list.reset(); while (list.hasNext()) { list.next(); if (value.equals(getCurrentDisplayValue())) { copyValues(); return; } } throw new NotInListException(NotInListException.GIVEN_VALUE_NOT_IN_LIST, value); } /** * Returns the currently selected item from the list. * @throws NotInListException if current valueHolder object values do not * match any item in list. * @throws ModelException if problem while matching properties of valueHolder * object to list object. */ public Object getSelectedItem() throws com.taursys.model.ModelException { if (positionListToCurrentSelection()) return list.getObject(); else return null; } /** * Sets the current selection by searching through the list for the given object. * This method also copies the corresponding values from the selected list * object to the valueHolder object. If the given value is null or a value * matching the nullDisplay, this model will set the propertyNames[] in the * valueHolder object to null. * @throws NotInListException if the given value does not match any item in list. * @throws ModelException if problem while setting properties of object in * valueHolder or fetching display values from the list. */ public void setSelectedItem(Object value) throws ModelException { if (value == null) { setNullValues(); return; } list.reset(); while (list.hasNext()) { list.next(); if (value.equals(list.getObject())) { copyValues(); return; } } throw new NotInListException(NotInListException.GIVEN_VALUE_NOT_IN_LIST, value.toString()); } /** * Returns a Collection of the SelectModelOptions. The SelectModelOption * has 2 properties: optionText which is the value to display in the list * and selected, which indicates whether or not the item is selected. * <p> * The nullDisplay value will be first in the collection. * @throws ModelException if problem while matching properties of valueHolder * object to list object. */ public Collection getDisplayOptionList() throws ModelException { ArrayList arrayList = new ArrayList(); boolean found = false; if (nullAllowed) { found = isValueNull(); arrayList.add(new SelectModelOption(nullDisplay, found)); } list.reset(); while (list.hasNext()) { list.next(); String displayValue = getCurrentDisplayValue(); if (isCurrentMatch()) { arrayList.add(new SelectModelOption(displayValue, true)); found = true; } else { arrayList.add(new SelectModelOption(displayValue, false)); } } if (!found) Debug.warn("Current selection not in list. " + getCurrentPropertyValues()); return arrayList; } // ************************************************************************* // Internal Methods // ************************************************************************* /** * Positions the list to the current selection as indicated by the valueHolder. * If the current selection is null then the list is put in the "reset" * position and this method returns false. * @returns true if valid position * @throws NotInListException if the current selection is not in the list. * @throws ModelException if problem while matching properties of valueHolder * object to list object. */ private boolean positionListToCurrentSelection() throws ModelException { list.reset(); if (isValueNull()) return false; while (list.hasNext()) { list.next(); if (isCurrentMatch()) return true; } throw new NotInListException(NotInListException.CURRENT_VALUE_NOT_IN_LIST); } /** * Returns the Display value of the current position in the option list. * This is not necessarily the currently selected item. * @throws ModelException if problem occurs while accessing value */ private String getCurrentDisplayValue() throws ModelException { Object value = getList().getPropertyValue(displayPropertyName); int javaDataType = getList().getJavaDataType(displayPropertyName); if (value == null) return ""; Format form = getFormat(); if (form == null) return DataTypes.format(javaDataType, value); else if (form instanceof MessageFormat) return ((MessageFormat)form).format(new Object[] {value}); else return form.format(value); } protected String getCurrentPropertyValues() throws ModelException { String values = ""; for (int i = 0; i < propertyNames.length; i++) values += propertyNames[i] + "=" + getValueHolder().getPropertyValue(propertyNames[i]); return values; } /** * Compares the corresponding property values of the current list with valueHolder object. * @returns true if values match * @throws ModelException if problem while accessing properties of valueHolder * object or list object. */ protected boolean isCurrentMatch() throws ModelException { validateProperties(); for (int i = 0; i < propertyNames.length; i++) { Object value = getValueHolder().getPropertyValue(propertyNames[i]); Object listValue = getList().getPropertyValue(listPropertyNames[i]); if ( value == null || !value.equals(listValue)) { return false; } } return true; } /** * Returns true if all of the properties (propertyNames[]) are null. * @throws ModelException if problem accessing properties of valueHolder object. */ protected boolean isValueNull() throws ModelException { validateProperties(); for (int i = 0; i < propertyNames.length; i++) { Object value = getValueHolder().getPropertyValue(propertyNames[i]); if ( value != null) { return false; } } return true; } /** * Checks integrity of propertyNames and listPropertyNames arrays. are not * @throws ModelException if either are null/empty or if different sizes. */ private void validateProperties() throws ModelException { if (propertyNames == null || listPropertyNames == null || propertyNames.length != listPropertyNames.length) { throw new SelectModelException( SelectModelException.REASON_HOLDER_LIST_MISMATCH, displayPropertyName, listPropertyNames, propertyNames, true, nullDisplay); } } /** * Copies the corresponding values from the current listObject to the valueHolder object. * @throws ModelException if problem copying values */ protected void copyValues() throws ModelException { validateProperties(); for (int i = 0; i < propertyNames.length; i++) { Object listValue = getList().getPropertyValue(listPropertyNames[i]); getValueHolder().setPropertyValue(propertyNames[i], listValue); } } /** * Sets the propertyNames[] in the valueHolder object to null. * @throws ModelException if problem occurs */ protected void setNullValues() throws ModelException { validateProperties(); for (int i = 0; i < propertyNames.length; i++) { getValueHolder().setPropertyValue(propertyNames[i], null); } } /** * Creates an ObjectValueHolder as the default ValueHolder for model. */ protected ValueHolder createDefaultValueHolder() { return new ObjectValueHolder(); } // ************************************************************************* // Property Accessor Methods // ************************************************************************* /** * Sets collectionValueHolder which holds the collection of possible selections. */ public void setList(com.taursys.model.CollectionValueHolder newList) { list = newList; } /** * Returns collectionValueHolder which holds the collection of possible selections. */ public com.taursys.model.CollectionValueHolder getList() { return list; } /** * Sets the property name of the list object to display in the list. * Default is "value". */ public void setDisplayPropertyName(String newDisplayPropertyName) { displayPropertyName = newDisplayPropertyName; } /** * Returns the property name of the list object to display in the list. * Default is "value". */ public String getDisplayPropertyName() { return displayPropertyName; } /** * Sets array of property names in list object to copy to valueHolder object. * Default is {"value"}. */ public void setListPropertyNames(String[] newListPropertyNames) { listPropertyNames = newListPropertyNames; } /** * Returns array of property names in list object to copy to valueHolder object. * Default is {"value"}. */ public String[] getListPropertyNames() { return listPropertyNames; } /** * Sets array of property names in valueHolder object that correspond to properties of list object. * Default is {"value"}. */ public void setPropertyNames(String[] newPropertyNames) { propertyNames = newPropertyNames; } /** * Gets array of property names in valueHolder object that correspond to properties of list object. * Default is {"value"}. */ public String[] getPropertyNames() { return propertyNames; } /** * Sets the property name in valueHolder that will hold the value from the list. * If you have also used the setPropertyNames method, then this will change the * first name in that array. */ public void setPropertyName(String newPropertyName) { propertyNames[0] = newPropertyName; } /** * Gets the property name in valueHolder that will hold the value from the list. * If you have also used the setPropertyNames method, then this will return the * first name in that array. */ public String getPropertyName() { return propertyNames[0]; } /** * Sets indicator that a null value is a valid selection. If true, the * <code>nullDisplay</code> value will appear in the list of options * generated by <code>getDisplayOptionList</code> and <code>setText</code> * will accept null or the <code>nullDisplay</code> value. If false, the * <code>setText</code> method will throw a <code>NotInListException</code> * if null or the <code>nullDisplay</code> value is set, and the * <code>nullDisplay</code> will not appear in the <code>displayOptionList</code>. * Default is true. */ public void setNullAllowed(boolean nullAllowed) { this.nullAllowed = nullAllowed; } /** * Returns indicator that a null value is a valid selection. If true, the * <code>nullDisplay</code> value will appear in the list of options * generated by <code>getDisplayOptionList</code> and <code>setText</code> * will accept null or the <code>nullDisplay</code> value. If false, the * <code>setText</code> method will throw a <code>NotInListException</code> * if null or the <code>nullDisplay</code> value is set, and the * <code>nullDisplay</code> will not appear in the <code>displayOptionList</code>. * Default is true. */ public boolean isNullAllowed() { return nullAllowed; } /** * Sets value to display in list for a null value. */ public void setNullDisplay(String newNullDisplay) { nullDisplay = newNullDisplay; } /** * Returns value to display in list for a null value. */ public String getNullDisplay() { return nullDisplay; } }